/*
 *	Pig Latin Mode for CodeMirror 2 
 *	@author Prasanth Jayachandran
 *	@link 	https://github.com/prasanthj/pig-codemirror-2
 *  This implementation is adapted from PL/SQL mode in CodeMirror 2.
 */
CodeMirror.defineMode("pig", function (config, parserConfig) {
    var indentUnit = config.indentUnit,
        keywords = parserConfig.keywords,
        builtins = parserConfig.builtins,
        types = parserConfig.types,
        multiLineStrings = parserConfig.multiLineStrings;

    var isOperatorChar = /[*+\-%<>=&?:\/!|]/;

    function chain(stream, state, f) {
        state.tokenize = f;
        return f(stream, state);
    }

    var type;

    function ret(tp, style) {
        type = tp;
        return style;
    }

    function tokenComment(stream, state) {
        var isEnd = false;
        var ch;
        while (ch = stream.next()) {
            if (ch == "/" && isEnd) {
                state.tokenize = tokenBase;
                break;
            }
            isEnd = (ch == "*");
        }
        return ret("comment", "comment");
    }

    function tokenString(quote) {
        return function (stream, state) {
            var escaped = false, next, end = false;
            while ((next = stream.next()) != null) {
                if (next == quote && !escaped) {
                    end = true;
                    break;
                }
                escaped = !escaped && next == "\\";
            }
            if (end || !(escaped || multiLineStrings))
                state.tokenize = tokenBase;
            return ret("string", "error");
        };
    }

    function tokenBase(stream, state) {
        var ch = stream.next();

        // is a start of string?
        if (ch == '"' || ch == "'")
            return chain(stream, state, tokenString(ch));
        // is it one of the special chars
        else if (/[\[\]{}\(\),;\.]/.test(ch))
            return ret(ch);
        // is it a number?
        else if (/\d/.test(ch)) {
            stream.eatWhile(/[\w\.]/);
            return ret("number", "number");
        }
        // multi line comment or operator
        else if (ch == "/") {
            if (stream.eat("*")) {
                return chain(stream, state, tokenComment);
            }
            else {
                stream.eatWhile(isOperatorChar);
                return ret("operator", "operator");
            }
        }
        // single line comment or operator
        else if (ch == "-") {
            if (stream.eat("-")) {
                stream.skipToEnd();
                return ret("comment", "comment");
            }
            else {
                stream.eatWhile(isOperatorChar);
                return ret("operator", "operator");
            }
        }
        // is it an operator
        else if (isOperatorChar.test(ch)) {
            stream.eatWhile(isOperatorChar);
            return ret("operator", "operator");
        }
        else {
            // get the while word
            stream.eatWhile(/[\w\$_]/);
            // is it one of the listed keywords?
            if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) {
                if (stream.eat(")") || stream.eat(".")) {
                    //keywords can be used as variables like flatten(group), group.$0 etc..
                }
                else {
                    return ("keyword", "keyword");
                }
            }
            // is it one of the builtin functions?
            if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase())) {
                return ("keyword", "variable-2")
            }
            // is it one of the listed types?
            if (types && types.propertyIsEnumerable(stream.current().toUpperCase()))
                return ("keyword", "variable-3")
            // default is a 'word'
            return ret("word", "pig-word");
        }
    }

    // Interface
    return {
        startState: function (basecolumn) {
            return {
                tokenize: tokenBase,
                startOfLine: true
            };
        },

        token: function (stream, state) {
            if (stream.eatSpace()) return null;
            var style = state.tokenize(stream, state);
            return style;
        }
    };
});

(function () {
    function keywords(str) {
        var obj = {}, words = str.split(" ");
        for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
        return obj;
    }

    // builtin funcs taken from trunk revision 1303237
    var pBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL "
        + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS "
        + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG "
        + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN "
        + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER "
        + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS "
        + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA  "
        + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE "
        + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG "
        + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER ";

    // taken from QueryLexer.g
    var pKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP "
        + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL "
        + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE "
        + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE "
        + "NEQ MATCHES TRUE FALSE ";

    // data types
    var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP "

    CodeMirror.defineMIME("text/x-pig", {
        name: "pig",
        builtins: keywords(pBuiltins),
        keywords: keywords(pKeywords),
        types: keywords(pTypes)
    });
}());
